/*
 *
 * Copyright (C) 2015 ng-voice GmbH, Carsten Bock, carsten@ng-voice.com
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "../cdp/cdp_load.h"
#include "../cdp_avp/cdp_avp_mod.h"
#include "ocs_avp_helper.h"
#include "ims_ocs_mod.h"

/**
 * Returns the value of a certain AVP from a Diameter message.
 * @param m - Diameter message to look into
 * @param avp_code - the code to search for
 * @param vendorid - the value of the vendor id to look for or 0 if none
 * @param func - the name of the calling function, for debugging purposes
 * @returns the str with the payload on success or an empty string on failure
 */
str get_avp(AAAMessage *msg, int avp_code, int vendor_id, const char *func)
{
	AAA_AVP *avp;
	str r = {0, 0};
	avp = cdpb.AAAFindMatchingAVP(msg, 0, avp_code, vendor_id, 0);
	if(avp == 0) {
		LM_INFO("%s: Failed finding avp\n", func);
		return r;
	} else
		return avp->data;
}

str getSession(AAAMessage *msg)
{
	AAA_AVP *avp;
	str r = {0, 0};
	avp = cdpb.AAAFindMatchingAVP(msg, 0, AVP_Session_Id, 0, 0);
	if(avp == 0) {
		LM_INFO("Failed finding avp\n");
		return r;
	} else
		return avp->data;
}

int getRecordNummber(AAAMessage *msg)
{
	AAA_AVP *avp;
	avp = cdpb.AAAFindMatchingAVP(msg, 0, AVP_Accounting_Record_Number, 0, 0);
	if(avp == 0) {
		LM_DBG("Failed finding avp\n");
		return 0;
	} else
		return get_4bytes(avp->data.s);
}

str getSubscriptionId1(AAAMessage *msg, int *type)
{
	AAA_AVP *avp, *avp_type, *avp_value;
	str r = {0, 0};
	avp = cdpb.AAAFindMatchingAVP(msg, 0, AVP_Subscription_Id, 0, 0);
	AAA_AVP_LIST list;
	list = cdp_avp->cdp->AAAUngroupAVPS(avp->data);
	avp_type = cdpb.AAAFindMatchingAVPList(
			list, list.head, AVP_Subscription_Id_Type, 0, 0);
	avp_value = cdpb.AAAFindMatchingAVPList(
			list, list.head, AVP_Subscription_Id_Data, 0, 0);

	if(avp_type) {
		*type = get_4bytes(avp_type->data.s);
	} else {
		LM_DBG("Failed finding type\n");
		*type = 0;
	}
	if(avp_value == 0) {
		LM_DBG("Failed finding value\n");
	} else {
		r = avp_value->data;
	}
	cdpb.AAAFreeAVPList(&list);
	return r;
}

int isOrig(AAAMessage *msg)
{
	AAA_AVP *service, *imsinfo, *role;
	AAA_AVP_LIST list, list2;

	int result = 0;
	service = cdpb.AAAFindMatchingAVP(
			msg, 0, AVP_IMS_Service_Information, IMS_vendor_id_3GPP, 0);
	if(service) {
		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head,
				AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
		if(imsinfo) {
			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
			role = cdpb.AAAFindMatchingAVPList(list2, list2.head,
					AVP_IMS_Role_Of_Node, IMS_vendor_id_3GPP, 0);
			if(role) {
				result = get_4bytes(role->data.s);
			}
			cdpb.AAAFreeAVPList(&list2);
		} else {
			LM_DBG("Failed finding IMS-Info\n");
		}
		cdpb.AAAFreeAVPList(&list);
	} else {
		LM_DBG("Failed finding Service-Info\n");
	}

	return result;
}

str getCalledParty(AAAMessage *msg)
{
	AAA_AVP *service, *imsinfo, *calledparty;
	str r = {0, 0};
	service = cdpb.AAAFindMatchingAVP(
			msg, 0, AVP_IMS_Service_Information, IMS_vendor_id_3GPP, 0);
	if(service) {
		AAA_AVP_LIST list, list2;
		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head,
				AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
		if(imsinfo) {
			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
			calledparty = cdpb.AAAFindMatchingAVPList(list2, list2.head,
					AVP_IMS_Called_Party_Address, IMS_vendor_id_3GPP, 0);
			if(calledparty) {
				r = calledparty->data;
			} else {
				LM_DBG("Failed finding value\n");
			}
			cdpb.AAAFreeAVPList(&list2);
		} else {
			LM_DBG("Failed finding IMS-Info\n");
		}
		cdpb.AAAFreeAVPList(&list);
	} else {
		LM_DBG("Failed finding Service-Info\n");
	}

	return r;
}

str getAccessNetwork(AAAMessage *msg)
{
	AAA_AVP *service, *imsinfo, *access;
	str r = {0, 0};
	service = cdpb.AAAFindMatchingAVP(
			msg, 0, AVP_IMS_Service_Information, IMS_vendor_id_3GPP, 0);
	if(service) {
		AAA_AVP_LIST list, list2;
		list = cdp_avp->cdp->AAAUngroupAVPS(service->data);
		imsinfo = cdpb.AAAFindMatchingAVPList(list, list.head,
				AVP_IMS_IMS_Information, IMS_vendor_id_3GPP, 0);
		if(imsinfo) {
			list2 = cdp_avp->cdp->AAAUngroupAVPS(imsinfo->data);
			access = cdpb.AAAFindMatchingAVPList(list2, list2.head,
					AVP_IMS_Access_Network_Information, IMS_vendor_id_3GPP, 0);
			if(access) {
				r = access->data;
			} else {
				LM_DBG("Failed finding value\n");
			}
			cdpb.AAAFreeAVPList(&list2);
		} else {
			LM_DBG("Failed finding IMS-Info\n");
		}
		cdpb.AAAFreeAVPList(&list);
	} else {
		LM_DBG("Failed finding Service-Info\n");
	}
	return r;
}

int getUnits(AAAMessage *msg, int *used, int *service, int *group)
{
	AAA_AVP *avp, *req_units, *value, *used_units, *service_avp, *rating_group;
	int units = 0;
	*used = 0;
	*service = 0;
	avp = cdpb.AAAFindMatchingAVP(
			msg, 0, AVP_Multiple_Services_Credit_Control, 0, 0);
	if(avp) {
		AAA_AVP_LIST list, list2;
		list = cdp_avp->cdp->AAAUngroupAVPS(avp->data);
		req_units = cdpb.AAAFindMatchingAVPList(
				list, list.head, AVP_Requested_Service_Unit, 0, 0);
		if(req_units) {
			list2 = cdp_avp->cdp->AAAUngroupAVPS(req_units->data);
			value = cdpb.AAAFindMatchingAVPList(
					list2, list2.head, AVP_CC_Time, 0, 0);
			cdpb.AAAFreeAVPList(&list2);
			if(value)
				units = get_4bytes(value->data.s);
			cdpb.AAAFreeAVPList(&list2);
		}
		service_avp = cdpb.AAAFindMatchingAVPList(
				list, list.head, AVP_Service_Identifier, 0, 0);
		if(service_avp) {
			*service = get_4bytes(service_avp->data.s);
		}
		used_units = cdpb.AAAFindMatchingAVPList(
				list, list.head, AVP_Used_Service_Unit, 0, 0);
		if(used_units) {
			list2 = cdp_avp->cdp->AAAUngroupAVPS(used_units->data);
			value = cdpb.AAAFindMatchingAVPList(
					list2, list2.head, AVP_CC_Time, 0, 0);
			if(value)
				*used = get_4bytes(value->data.s);
			cdpb.AAAFreeAVPList(&list2);
		}
		rating_group = cdpb.AAAFindMatchingAVPList(
				list, list.head, AVP_Rating_Group, 0, 0);
		if(rating_group) {
			*group = get_4bytes(rating_group->data.s);
		}
		cdpb.AAAFreeAVPList(&list);
	}
	if(*service == 0)
		LM_WARN("Failed to get service-identifier\n");
	return units;
}


/**
 * Create and add an AVP to a Diameter message.
 * @param m - Diameter message to add to
 * @param d - the payload data
 * @param len - length of the payload data
 * @param avp_code - the code of the AVP
 * @param flags - flags for the AVP
 * @param vendorid - the value of the vendor id or 0 if none
 * @param data_do - what to do with the data when done
 * @param func - the name of the calling function, for debugging purposes
 * @returns 1 on success or 0 on failure
 */
int ocs_add_avp(AAAMessage *m, char *d, int len, int avp_code, int flags,
		int vendorid, int data_do, const char *func)
{
	AAA_AVP *avp;
	if(vendorid != 0)
		flags |= AAA_AVP_FLAG_VENDOR_SPECIFIC;
	avp = cdpb.AAACreateAVP(avp_code, flags, vendorid, d, len, data_do);
	if(!avp) {
		LM_ERR("%s: Failed creating avp\n", func);
		return 0;
	}
	if(cdpb.AAAAddAVPToMessage(m, avp, m->avpList.tail) != AAA_ERR_SUCCESS) {
		LM_ERR("%s: Failed adding avp to message\n", func);
		cdpb.AAAFreeAVP(&avp);
		return 0;
	}
	return 1;
}


/**
 * Create and add an AVP to a list of AVPs.
 * @param list - the AVP list to add to
 * @param d - the payload data
 * @param len - length of the payload data
 * @param avp_code - the code of the AVP
 * @param flags - flags for the AVP
 * @param vendorid - the value of the vendor id or 0 if none
 * @param data_do - what to do with the data when done
 * @param func - the name of the calling function, for debugging purposes
 * @returns 1 on success or 0 on failure
 */
int ocs_add_avp_list(AAA_AVP_LIST *list, char *d, int len, int avp_code,
		int flags, int vendorid, int data_do, const char *func)
{
	AAA_AVP *avp;
	if(vendorid != 0)
		flags |= AAA_AVP_FLAG_VENDOR_SPECIFIC;
	avp = cdpb.AAACreateAVP(avp_code, flags, vendorid, d, len, data_do);
	if(!avp) {
		LM_ERR("%s: Failed creating avp\n", func);
		return 0;
	}
	if(list->tail) {
		avp->prev = list->tail;
		avp->next = 0;
		list->tail->next = avp;
		list->tail = avp;
	} else {
		list->head = avp;
		list->tail = avp;
		avp->next = 0;
		avp->prev = 0;
	}

	return 1;
}

int ocs_build_answer(AAAMessage *ccr, AAAMessage *cca, int result_code,
		int granted_units, int final_unit)
{
	AAA_AVP *avp;
	AAA_AVP_LIST granted_list, mscc_list, final_list;
	char x[4];
	str granted_group, mscc_group, final_group;
	int service, group, used;

	if(!ccr)
		return 0;
	if(!cca)
		return 0;

	// Set some basic data: Application-ID, CCR-Type, CCR-Request-Number
	set_4bytes(x, IMS_Ro);
	ocs_add_avp(cca, x, 4, AVP_Acct_Application_Id, AAA_AVP_FLAG_MANDATORY, 0,
			AVP_DUPLICATE_DATA, __FUNCTION__);

	avp = cdpb.AAAFindMatchingAVP(ccr, 0, AVP_IMS_CCR_Type, 0, 0);
	ocs_add_avp(cca, avp->data.s, avp->data.len, AVP_IMS_CCR_Type,
			AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);

	avp = cdpb.AAAFindMatchingAVP(ccr, 0, AVP_CC_Request_Number, 0, 0);
	ocs_add_avp(cca, avp->data.s, avp->data.len, AVP_CC_Request_Number,
			AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);

	// Result-Code:
	set_4bytes(x, result_code);
	ocs_add_avp(cca, x, 4, AVP_Result_Code, AAA_AVP_FLAG_MANDATORY, 0,
			AVP_DUPLICATE_DATA, __FUNCTION__);

	if(result_code == DIAMETER_SUCCESS) {
		granted_list.head = 0;
		granted_list.tail = 0;
		final_list.head = 0;
		final_list.tail = 0;
		mscc_list.head = 0;
		mscc_list.tail = 0;

		getUnits(ccr, &used, &service, &group);

		set_4bytes(x, group);
		ocs_add_avp_list(&mscc_list, x, 4, AVP_Rating_Group,
				AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);

		set_4bytes(x, service);
		ocs_add_avp_list(&mscc_list, x, 4, AVP_Service_Identifier,
				AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);

		if(granted_units > 0) {
			set_4bytes(x, granted_units);
			ocs_add_avp_list(&granted_list, x, 4, AVP_CC_Time,
					AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA,
					__FUNCTION__);
			granted_group = cdpb.AAAGroupAVPS(granted_list);
			cdpb.AAAFreeAVPList(&granted_list);
			ocs_add_avp_list(&mscc_list, granted_group.s, granted_group.len,
					AVP_Granted_Service_Unit, AAA_AVP_FLAG_MANDATORY, 0,
					AVP_FREE_DATA, __FUNCTION__);
		}

		// Result-Code:
		set_4bytes(x, result_code);
		ocs_add_avp_list(&mscc_list, x, 4, AVP_Result_Code,
				AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);

		set_4bytes(x, 86400);
		ocs_add_avp_list(&mscc_list, x, 4, AVP_Validity_Time,
				AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__);


		if(final_unit > 0) {
			set_4bytes(x, 0);
			ocs_add_avp_list(&final_list, x, 4, AVP_Final_Unit_Action,
					AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA,
					__FUNCTION__);
			final_group = cdpb.AAAGroupAVPS(final_list);
			cdpb.AAAFreeAVPList(&final_list);
			ocs_add_avp_list(&mscc_list, final_group.s, final_group.len,
					AVP_Final_Unit_Indication, AAA_AVP_FLAG_MANDATORY, 0,
					AVP_FREE_DATA, __FUNCTION__);
		}


		mscc_group = cdpb.AAAGroupAVPS(mscc_list);
		cdpb.AAAFreeAVPList(&mscc_list);

		return ocs_add_avp(cca, mscc_group.s, mscc_group.len,
				AVP_Multiple_Services_Credit_Control, AAA_AVP_FLAG_MANDATORY, 0,
				AVP_FREE_DATA, __FUNCTION__);
	}
	return 1;
}
